/**
* @file
* @brief This file handles all server related functions.
*/


//-------------------------------------------------------
// Header Files
//-------------------------------------------------------
#include "server.h" // for client structure
#include <stdio.h>
#include <stdlib.h>

//-------------------------------------------------------
// Constants
//-------------------------------------------------------
#define MAX_LISTENQUEUELEN 20   // The maximum number of queued connections.
#define LOGGING 2
    // 0 -> disabled logging
    // 1 -> log to stdout
    // 2 -> log to file that follows format: Client/Server-yyyy-mm-dd-hh-ss.log
//-------------------------------------------------------
// Function Prototypes
//-------------------------------------------------------


//-------------------------------------------------------
// Main
//-------------------------------------------------------
/**
* Main function that parses in commandline arguments and initializes the server
* @param argv[1] configurationFile
*/
int main(int argc, char *argv[])
{
/* TEMP FOR DEBUGGING
    addclient(20);
    printf("Top fd is %d\n", top->fd);
*/
/* // These are in storage.c for M2, need to change for M4 when multi client
    int authen = 0; // single client server not authenticated
    int connected = 0; // single client server not connected
*/

    int totalProcTime = 0;	// total Processing Time
    struct timeval start_time, end_time;	//Same as client.c implementation
    int tempT = 0;
    char timeOut[64];
    // Process command line arguments.
    // This program expects exactly one argument: the config file name.
    assert(argc > 0); // aborts program if this is false
    if (argc != 2)
    {
	printf("Usage %s <config_file>\n", argv[0]);
	exit(EXIT_FAILURE);
    }
    // Configuration file
    char *config_file = argv[1];
    // Read the config_file and store into the config_params struct
    struct config_params params; // structure to marshall and store configuration parameters into a structure
    struct check_duplmis check; // structure to check duplicates of configuration parameters
    initialCheck(&check);
    // @note Table is currently defined in server.c only
    // Initialize table as a linked list
    initializeSortedArrayTable();
    // @note CTRL+C automatically frees all memory according to T.A
    // Read the configuration file and check to make sure it meets the configuration file requirements
    int status = read_config(config_file, &params, &check); // returns 0 if successful
    if (status != 0)
    {
	perror("Error processing config file.");
	exit(EXIT_FAILURE);
    }
    // Sort the tables
    sortTable(); // TODO: Change this to merge/quick sort instead of bubble sort
    // Set up time for logging
    char fileName[MAX_STRTYPE_SIZE];
    time_t basetime;
    struct tm * conTime;
    basetime = time(NULL);
    conTime = localtime(&basetime);
    strftime(fileName, sizeof(fileName), "Server-%Y-%m-%d-%H-%M-%S.log",conTime);
    int logChoice = LOGGING;
    // Initialize fileServer based on LOGGING
    switch(logChoice)
    {
	case 0: // Disable Logging
	    fileServer = NULL;
	    break;
	case 1: // Log to stdout
	    fileServer = stdout;
	    break;
	case 2: // Log to File
	    // Append file for appending an update
	    fileServer = fopen (fileName,"a+");
	    if (fileServer == NULL)
	    {
		printf("Error opening file.\n");
		fclose(fileServer);
		exit(EXIT_FAILURE);
	    }
	    break;
    }
    // Server on localhost:1376
    sprintf(message, "Server on %s:%d\n", params.server_host, params.server_port);
    logger (fileServer, message);
    // Create a socket for the server
    // listensock is the sockfd to be listening to
    int listensock = socket(PF_INET, SOCK_STREAM, 0);
    if (listensock < 0)
    {
	perror("Error creating socket.");
	Fclose(logChoice, 1);
	exit(EXIT_FAILURE);
    }
    // Allow listening port to be reused if defunctional
    int yes = 1;
    status = setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
    if (status != 0)
    {
	perror("Error configuring socket.");
	Fclose(logChoice, 1);
	exit(EXIT_FAILURE);
    }
    // Bind to the listening port.
    struct sockaddr_in listenaddr;
    memset(&listenaddr, 0, sizeof listenaddr);
    listenaddr.sin_family = AF_INET;
    listenaddr.sin_port = htons(params.server_port);
    inet_pton(AF_INET, params.server_host, &(listenaddr.sin_addr)); // bind to local IP address
    // Let server bind to this socket
    status = bind(listensock, (struct sockaddr*) &listenaddr, sizeof listenaddr);
    if (status != 0)
    {
	perror("Error binding socket.");
	Fclose(logChoice, 1);
	exit(EXIT_FAILURE);
    }
    // Start listening for connections.
    status = listen(listensock, MAX_LISTENQUEUELEN);
    if (status != 0)
    {
	perror("Error listening on socket.");
	Fclose(logChoice, 1);
	exit(EXIT_FAILURE);
    }
    // Listen loop.
    int wait_for_connections = 1;
    // Infinite while loop for milestone 1
    while (wait_for_connections)
    {
	// Wait for a connection.
	struct sockaddr_in clientaddr;
	socklen_t clientaddrlen = sizeof clientaddr;
	// Accept any calls from any client
	// @note: Will just poll till a client is accepted
	// @note: Right now only works with 1 clientsock
	int clientsock = accept(listensock, (struct sockaddr*)&clientaddr, &clientaddrlen);
	// Add this client to linked list of clients
	addclient(clientsock);
	// Accepted client wil return it's file descriptor as clientsock
	if (clientsock < 0)
	{
/*errno = ERR_CONNECTION_FAIL; // No error handling in server.c */
	    perror("Error accepting a connection.");
	    Fclose(logChoice, 1);
	    exit(EXIT_FAILURE);
	}
	sprintf(message, "Got a connection from %s:%d.\n", inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port);
	logger(fileServer, message);
	// TODO: For Milestone 3, change code to work with > 1 client
	// Get commands from client.
	int wait_for_commands = 1;
	do
	{
	    // Read a line from the client.
	    char cmd[MAX_CMD_LEN];
	    // Read from clientsock's file descriptor to command
	    int status = recvline(clientsock, cmd, MAX_CMD_LEN);
	    if (status != 0)
	    {
		// Here, either an error occurred or the client closed the connection.
		// Stop waiting for commands
		wait_for_commands = 0;
	    }
	    else
	    {
		// Handle the command from the client.
		// @note: For milestone1, this just prints it back out to server
		int status = handle_command(clientsock, cmd, &params);
		// If handled command fails, stop waiting for command
		if (status != 0)
		{
		    perror("Error: Handle command return status of -1 to main() in server");
		    wait_for_commands = 0; // stop waiting for commands
		    exit(EXIT_FAILURE); // @note original code for M1 by prof. was to just EXIT_SUCCESS at bottom
		}
            }
         }
	while (wait_for_commands); //do while client is still interacting
        // Close the connection with the client.
//	close(clientsock);
	sprintf(message, "Closed connection from %s:%d.\n", inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port);
	logger(fileServer, message);
    }
    // Stop listening for connections.
    close(listensock);
    return EXIT_SUCCESS;
}

//-------------------------------------------------------
// Helper Functions
//-------------------------------------------------------
/**
* This function handles a command given by sending it as a string to a socket given.
* @param sock The socket descriptor to output commands to
* @param cmd A string to be sent to a socket descriptor
* @returns 0 if success, returns error code if failure
*/
int handle_command(int sock, char *cmd, struct config_params *params)
{
    char table[MAX_TABLE_LEN] = "a", key[MAX_KEY_LEN+1] = "a", username[MAX_USERNAME_LEN+1] = "a", password[MAX_ENC_PASSWORD_LEN+1] = "a";
    struct storage_record stTemp;
    char value[sizeof stTemp.value];
    char* args[4];
    // Get arguments into args and numArgs holds number of arguments given whic is 3 or 4.
    // @note: Need to free all args once done
    int numArgs = getArguments(args, cmd);
    // Note: Must free all args later
    int i = 0;
    sprintf(message, "Processing command '%s'\n", cmd);
    logger(fileServer, message);
    // @note: sock is the socket file descriptor (sockfd)
    // Send the command out to sockfd, which is the server
    // and end with a new line
// printf("arguments receive are:\n%s %s %s %s\n", args[0], args[1], args[2], args[3]);
    // Handle Commands
    //--------------------------------------------
    // GET table key (NULL)
    //--------------------------------------------
    if (strcmp(args[0], "GET") == 0)
    {
	if(findTableName(args[1]) == 0)
	{
            sprintf(message, "-2\n"); // 1=> success
            sendall(sock, message, strlen(message));
	    return ERR_TABLE_NOT_FOUND; // table not found
	}
	char data[100];
	int getStat = getValue(args[1], args[2], data); // get the value on args[3] 
	switch (getStat)
	{
	    case -1:
		sprintf(message, "-1\n");
		sendall(sock, message, strlen(message));
		return ERR_KEY_NOT_FOUND;
		break;
	    case 1:
		// Send success and actual message together
		sprintf(message, "1,%s\n", data);
		sendall(sock, message, strlen(message));
		return 0;
		break;
	    default: // should never be here
		break;
	}
	
    }

    //--------------------------------------------
    // SET table key value
    //--------------------------------------------
    if (strcmp(args[0], "SET") == 0)
    {
	if(findTableName(args[1]) == 0)
	{
            sprintf(message, "-2\n"); // 1=> success
            sendall(sock, message, strlen(message));
	    return ERR_TABLE_NOT_FOUND; // table not found
	}
	// If given NULL string as input, args 3 should be NULL 
	if (strcmp(args[3], "NULL") == 0 || strcmp(args[3], "") == 0)
	   args[3] = NULL;
	int insertStat = insertValue(args[1], args[2], args[3]);
	switch (insertStat)
	{
	    case -1:
		sprintf(message, "-1\n");
		sendall(sock, message, strlen(message));
		return ERR_KEY_NOT_FOUND;
		break;
	    case 1:
		sprintf(message, "1\n");
		sendall(sock, message, strlen(message));
		return 0;
		break;
	    case 2:
		sprintf(message, "2\n");
		sendall(sock, message, strlen(message));
		return 0;
		break;
	    case 3:
		sprintf(message, "3\n");
		sendall(sock, message, strlen(message));
		return 0;
		break;
	    default:
		break;
	}
    }
    //--------------------------------------------
    // AUTH username encrptpassword (NULL)
    //--------------------------------------------
    if (strcmp(args[0], "AUTH") == 0)
    {
	if (strcmp(args[1], params->username) == 0)
	{
	    if(strcmp(args[2], params->password) == 0)
	    {
		struct client* newclient;
//TODO: account for case where client not connected, make a check_connect() and check_auth() function
		newclient = getclient(sock);
		strcpy(newclient->username, params->username);
		strcpy(newclient->cryptpasswd, params->password);
		newclient->auth = true;
		sprintf(message, "1\n"); // 1=> success
		sendall(sock, message, strlen(message));
		return 0;
	    }
	}
	sprintf(message, "0\n");
	sendall(sock, message, strlen(message));
	return 0;
    }
    // @note free(args[i]) here will crash, not sure if need to free
    return 0;
}
